﻿using System.Reflection;
using System.Reflection.Extension;
using UnityEngine;
using UnityEngine.Extension;
using System.Linq;
using UnityEngine.UI;
using System.Collections.Generic;
using System.Collections;
using System.Xml.Serialization;

namespace System.Extension
{
    public static class objectExtension
    {
        #region Reflection
        static BindingFlags preferedFlag = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Static;

        public static void ForEachFieldInfo(this object source, Action<FieldInfo> callBack)
        {
            Type type = source.GetType();
            FieldInfo[] fieldInfos = type.GetFields(preferedFlag);
            foreach (FieldInfo fieldInfo in fieldInfos)
                callBack(fieldInfo);
        }

        public static void ForEachPropertyInfo(this object source, Action<PropertyInfo> callBack)
        {
            Type type = source.GetType();
            PropertyInfo[] propertyInfos = type.GetProperties(preferedFlag);
            foreach (PropertyInfo propertyInfo in propertyInfos)
                callBack(propertyInfo);
        }

        public static void ForEachMemberInfo(this object source, Action<MemberInfo> callBack)
        {
            source.ForEachFieldInfo(f => callBack(f));
            source.ForEachPropertyInfo(p => callBack(p));
        }

        public static void ForEachMethodInfo(this object source, Action<MethodInfo> callBack)
        {
            Type type = source.GetType();
            MethodInfo[] methodInfos = type.GetMethods(preferedFlag);
            foreach (MethodInfo methodInfo in methodInfos)
                callBack(methodInfo);
        }

        public static List<string> MemberPaths(this object source, string rPath = "", params Type[] inclusives)
        {
            List<string> result = new List<string>();

            source.ForEachMemberInfo(member => {
                if (!member.IsSameOrSubclass(typeof(UnityEngine.Object)) && !member.isGeneric())
                {
                    if (member.isClass())
                    {
                        object value = member.GetValue(source);
                        if (value != null)
                            result.AddRange(value.MemberPaths(rPath + member.Name + "/", inclusives));
                    }
                    else if (member.isInclusive(inclusives) || inclusives.Length < 1)
                        result.Add(rPath + member.Name);
                }
            });

            if (source is GameObject)
                (source as GameObject).ForEachComponent<Component>(c =>
                   result.AddRange(c.MemberPaths(c.GetType().Name + "/", inclusives)));
            return result;
        }

        public static List<string> MethodsPath(this object source, string rPath = "")
        {
            List<string> result = new List<string>();
            bool isGameObject = source is GameObject;
            source.ForEachMethodInfo(member => result.Add( (isGameObject ? "GameObject/" : "")+ rPath + member.Name ));

            if (isGameObject)
                (source as GameObject).ForEachComponent<Component>(c =>
                   result.AddRange(c.MethodsPath(c.GetType().Name + "/")));
            return result;
        }
        public static List<string> MethodTypePath(this Type type, string rPath = "")
        {
            List<string> result = new List<string>();
            type.ForEachMethodInfo(member => result.Add(rPath + member.Name));
            return result;
        }

        public static List<string> MemberPaths(this object source, Func<MemberInfo, bool> predicate, string rPath = "")
        {
            List<string> result = new List<string>();
            bool isGameObject = source is GameObject;

            source.ForEachMemberInfo(member => {
                if (predicate(member))
                    result.Add((isGameObject ? "GameObject/" : "") + rPath + member.Name);
                if (member.isClass() && !member.IsSameOrSubclass(typeof(UnityEngine.Object)))
                {
                    object value = member.GetValue(source);
                    if (value != null) result.AddRange(value.MemberPaths(predicate, rPath + member.Name + "/"));
                }
            });

            if (isGameObject)
                (source as GameObject).ForEachComponent<Component>(c =>
                   result.AddRange(c.MemberPaths(predicate, c.GetType().Name + "/")));
            return result;
        }

        public static List<string> MemberPathsInclusiveTypes(this object source, bool genericOnly, string rPath, params Type[] types)
        {
            List<string> result = new List<string>();
            bool isGameObject = source is GameObject;

            source.ForEachMemberInfo(member =>
            {
                if (Char.IsLetter(member.Name[0]))
                {
                    if(genericOnly && member.isGeneric())
                        result.Add((isGameObject ? "GameObject/" : "") + rPath + member.Name);
                    else if((member.isInclusive(types) || types.Length == 0))
                        result.Add((isGameObject ? "GameObject/" : "") + rPath + member.Name);
                    if (member.isClass() && !member.IsSameOrSubclass(typeof(UnityEngine.Object)))
                    {
                        object value = member.GetValue(source);
                        if (value != null) result.AddRange(value.MemberPathsInclusiveTypes(genericOnly, rPath + member.Name + "/", types));
                    }
                }
            });

            if (isGameObject)
                (source as GameObject).ForEachComponent<Component>(c =>
                   result.AddRange(c.MemberPathsInclusiveTypes(genericOnly, c.GetType().Name + "/", types)));
            return result;
        }

        public static List<string> MemberPathsFromType(this Type source, bool genericOnly, string rPath, params Type[] types)
        {
            List<string> result = new List<string>();

            source.ForEachMemberInfo(member =>
            {
                if (Char.IsLetter(member.Name[0]))
                {
                    if (genericOnly && member.isGeneric())
                        result.Add(rPath + member.Name);
                    else if (member.isInclusive(types) || types.Length == 0)
                        result.Add(rPath + member.Name);
                    if (member.isClass() && !member.IsSameOrSubclass(typeof(UnityEngine.Object)))
                    {
                        Type value = member.GetMemberType();
                        if (value != null) result.AddRange(value.MemberPathsFromType(genericOnly, rPath + member.Name + "/", types));
                    }
                }
            });
            return result;
        }

        public static List<string> MemberTypePaths(this Type source, Func<MemberInfo, bool> predicate, string rPath = "")
        {
            List<string> result = new List<string>();

            source.ForEachMemberInfo(member => {
                if (predicate(member))
                    result.Add(rPath + member.Name);
                if (member.isClass() && !member.IsSameOrSubclass(typeof(UnityEngine.Object)))
                {
                    Type value = member.GetMemberType();
                    if (value != null) result.AddRange(value.MemberTypePaths(predicate, rPath + member.Name + "/"));
                }
            });
            return result;
        }

        public static void ForEachMemberAttribute<T>(this object source, Action<MemberInfo, T> callBack) where T : Attribute
        {
            source.ForEachMemberInfo(m => {
                m.ForEachAttribute(a =>  {
                    if (a is T) callBack(m, a as T);
                });
            });
        }

        public static T GetAttribute<T>(this object source, string memberName) where T : Attribute
        {
            T result = default(T);
            MemberInfo memberInfo = source.GetMemberInfo(memberName);
            Attribute[] attributes = (Attribute[])memberInfo.GetCustomAttributes(true);
            result = attributes.FirstOrDefault(a => a is T) as T;
            return result;
        }

        public static MemberInfo SelectMemberInfo(this object source, Func<MemberInfo, bool> callBack)
        {
            MemberInfo result = null;
            source.ForEachFieldInfo(f => { if (callBack(f)) { result = f; } });
            if (result == null)
                source.ForEachPropertyInfo(p => { if (callBack(p)) { result = p; } });
            return result;
        }

        public static void ForEachMemberInfo(this object source, Action<MemberInfo, string> callBack, params Type[] inclusives)
        {
            source.ForEachMemberInfo(m =>
            {
                if (!m.IsSameOrSubclass(typeof(UnityEngine.Object))  && !m.isGeneric())
                {
                    string path = m.Name;
                    if(inclusives == null || inclusives.Length <= 0)
                        callBack(m, path);
                    else if (m.isInclusive(inclusives))// if (m.isPrimitive())
                        callBack(m, path);

                    if (m.isClass())
                    {
                        object val = m.GetValue(source);
                        if (val != null)    val.ForEachMemberInfo((c, p) => callBack(c, path + ("/" + p)), inclusives);
                    }
                }
            });
        }
        #endregion

        #region System
        public static bool isNull(this object source)
        {
            return source == null;
        }
        public static bool isNotNull(this object source)
        {
            return source != null;
        }
        #endregion


        public static object GetComponent(this object source, string childPath, string[] pathComponents, ref int pathIndex)
        {
            object result = source;

            if (childPath.isNotEmpty())
            {
                Transform child = ((GameObject)source).transform.FindChild(childPath);
                if (child) result = child.gameObject;
            }
            if (pathComponents.isNotNull())
            {
                Component component = ((GameObject)result).GetComponent(pathComponents[pathIndex]);
                if (component.isNotNull())
                {
                    result = component;
                    pathIndex++;
                }
            }
            return result;   // first path component is consumed.
        }

        public static MemberInfo GetMemberInfo(this object source, string memberPath)
        {
            MemberInfo result = null;
            if (source.isNotNull())
            {
                result = source.GetType().GetField(memberPath, preferedFlag);
                if (result == null)
                    result = source.GetType().GetProperty(memberPath, preferedFlag);
            }
            return result;
        }

        /*
        public static object GetValue(this object source, string memberPath, string childPath = "")
        {
            object parent = source.GetParent(memberPath, childPath);
            MemberInfo childInfo = source.GetChildMemberInfo(memberPath, childPath);
            return childInfo.GetValue(parent);
        }

        public static void SetValue(this object source, object value, string memberPath, string childPath = "")
        {
            object parent = source.GetParent(memberPath, childPath);
            MemberInfo childInfo = source.GetChildMemberInfo(memberPath, childPath);
            childInfo.SetValue(parent, value);
        }
        */

        public static object GetParent(this object source, string[] pathComponents, int startIndex = 0)
        {
            object result = source;
            MemberInfo memberInfo = null;
            int lastIndex = pathComponents.Length - 1;

            for (int index = startIndex; index < lastIndex; index++)
            {
                memberInfo = result.GetMemberInfo(pathComponents[index]);
               if (memberInfo.isNotNull()) result = memberInfo.GetValue(result);
                    else { break; }
            }
            return result;
        }
        /*
         public static object GetParent(this object source, string[] pathComponents, int startIndex = 0)
        {
            object result = source;
            MemberInfo memberInfo = null;
            int lastIndex = pathComponents.Length - 1;

            for (int index = startIndex; index < pathComponents.Length; index++)
            {
                memberInfo = result.GetMemberInfo(pathComponents[index]);
                if (index < lastIndex)
                    if (memberInfo.isNotNull()) result = memberInfo.GetValue(result);
                    else { break; }
            }
            return result;
        }
        */

        public static object GetParent(this object source, string memberPath, string childPath = "")
        {
            object result = source;
            string[] pathComponents = memberPath.GetComponents();
            int pathIndex = 0;

            if (source is GameObject)
                result = source.GetComponent(childPath, pathComponents, ref pathIndex); // first path component is consumed.

            if (memberPath.isNotEmpty())
            {
                result = result.GetParent(pathComponents, pathIndex);
            }
            return result;
        }

        public static MemberInfo GetChildMemberInfo(this object source, string[] pathComponents, int startIndex = 0)
        {
            object result = source;
            MemberInfo memberInfo = null;
            int lastIndex = pathComponents.Length - 1;

            for (int index = startIndex; index < pathComponents.Length; index++)
            {
                memberInfo = result.GetMemberInfo(pathComponents[index]);
                if (index < lastIndex)
                    if (memberInfo.isNotNull()) result = memberInfo.GetValue(result);
                    else { break; }
            }
            return memberInfo;
        }

        public static MemberInfo GetChildMemberInfo(this object source, string memberPath, string childPath = "")
        {
            object result = source;
            MemberInfo memberInfo = null;
            string[] pathComponents = memberPath.GetComponents();
            int pathIndex = 0;

            if (source is GameObject)
                result = source.GetComponent(childPath, pathComponents, ref pathIndex); // first path component is consumed.

            if (memberPath.isNotEmpty())
               memberInfo = result.GetChildMemberInfo(pathComponents, pathIndex);
            return memberInfo;
        }

        public static MethodInfo GetMethodInfo(this object source, string methodPath, string childPath = "")
        {
            object result = source;
            MethodInfo methodInfo = null;
           string[] pathComponents = methodPath.GetComponents();
            int pathIndex = 0;

            if (source is GameObject)
                result = source.GetComponent(childPath, pathComponents, ref pathIndex); // first path component is consumed.

            if (!methodPath.Equals(string.Empty))  methodInfo = result.GetType().GetMethod(pathComponents[pathIndex], preferedFlag);
            return methodInfo;
        }

        public static object InvokeMethod(this object source, string methodPath, object[] parameters = null, string childPath = "")
        {
            object result = source;
            object returnVal = null;
            string[] pathComponents = methodPath.GetComponents();
            int pathIndex = 0;

            if (source is GameObject)
                result = source.GetComponent(childPath, pathComponents, ref pathIndex); // first path component is consumed.

            if (!methodPath.Equals(string.Empty))
            {
                MethodInfo methodInfo = result.GetType().GetMethod(pathComponents[pathIndex], preferedFlag);
                if (methodInfo != null)
                  returnVal =  methodInfo.Invoke(result, parameters);
            }
            return returnVal;
        }
        public static object GetValue(this object source, string[] pathComponents, int startIndex = 0)
        {
            object result = source;
            for (int index = startIndex; index < pathComponents.Length; index++)
            {
                MemberInfo memberInfo = result.GetMemberInfo(pathComponents[index]);
                if (memberInfo != null) result = memberInfo.GetValue(result);
                else { result = null; break; }
            }
            return result;
        }

        public static object GetValue(this object source, string memberPath, string childPath = "")
        {
            object result = source;
            string[] pathComponents = memberPath.GetComponents();
            int pathIndex = 0;

            if (source is GameObject)
                result = source.GetComponent(childPath, pathComponents, ref pathIndex); // first path component is consumed.
            if (!memberPath.Equals(string.Empty))
                result = result.GetValue(pathComponents, pathIndex);

            return result;
        }

        public static void SetValue(this object source, object value, string[] pathComponents, int startIndex = 0)
        {
            object result = source;
            MemberInfo memberInfo = null;
            int lastIndex = pathComponents.Length - 1;

            for (int index = startIndex; index < pathComponents.Length; index++)
            {
                memberInfo = result.GetMemberInfo(pathComponents[index]);
                if (index < lastIndex)
                    if (memberInfo != null) result = memberInfo.GetValue(result);
                    else { result = null; break; }
            }
            if (result != null)
            {
                //error bound is important...
                if ((result is RawImage) && value.isNotNull())
				{
#if WRB_HIGHSTAKES
					value = AssetBundles.LoadResource((string)value);
#else

                    value = UnityEngine.Resources.Load((string)value);  // similarly for sprites..
#endif
				}
                if(value.isNotNull())
                memberInfo.SetValue(result, value);
            }
        }

        public static void SetValue(this object source, object value, string memberPath, string childPath )
        {
            object result = source;
            string[] pathComponents = memberPath.GetComponents();
            int pathIndex = 0;

            if (source is GameObject)
                result = source.GetComponent(childPath, pathComponents, ref pathIndex); // first path component is consumed.

            if (memberPath.isNotEmpty())
                result.SetValue(value, pathComponents, pathIndex);
        }

        public static void Presist(this object source, object value)
        {
            Type type = source.GetType();
            if (type.isGeneric())
            {
                List<object> vList = new List<object>();
                foreach (var item in ((IEnumerable)value))
                    vList.Add(item);
                foreach (var item in ((IEnumerable)source))
                {
                    item.Presist(vList[0]);
                    vList.RemoveAt(0);
                }
            }
            else
            {
                source.ForEachMemberAttribute<Persist>((m, a) => {
                object val = m.GetValue(value);
                m.SetValue(source, val);
                });

                source.ForEachMemberInfo(m =>
                {
                    if ((m.isClass() || m.isGeneric()) && !m.HasAttribute<XmlIgnoreAttribute>() ) // &&  (m.GetAttribute<XmlAttributeAttribute>()!=null) )
                    {
                        object sVal = m.GetValue(source), dVal = m.GetValue(value);
                        sVal.Presist(dVal);
                    }
                });
            }
            
            
        }

    }
}
   /*
        public static void ForEachMemberInfo(this object source, Action<MemberInfo> callBack, params Type[] inclusives)
        {
            source.ForEachFieldInfo(f =>
            {
                if((inclusives == null || inclusives.Length <= 0))// && !f.IsSameOrSubclass(typeof(UnityEngine.Object)) && !f.isGeneric())
                    callBack(f);
                else if (f.isInclusive(inclusives) &&  !f.IsSameOrSubclass(typeof(UnityEngine.Object)) && !f.isGeneric())
                    callBack(f);
            });
            source.ForEachPropertyInfo(p =>
            {
                if ((inclusives == null || inclusives.Length <= 0)) //&& !p.IsSameOrSubclass(typeof(UnityEngine.Object)) && !p.isGeneric())
                    callBack(p);
                else if (p.isInclusive(inclusives) && !p.IsSameOrSubclass(typeof(UnityEngine.Object)) && !p.isGeneric())
                    callBack(p);
            });
        }
        */
     
